﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace SMTP.Test.Client.Requests
{
    class PortScanTest
    {
        public event EventHandler<PortScanResults> ScanComplete;
        public string Host { get; set; }
        public int Port { get; set; }
        
        //default timeout
        const int TIMEOUT = 5000;
        int Timeout;

        //cancellation token - will come in handy for port scans
        CancellationTokenSource _tokenSource;

        public PortScanTest()
        {
            Host = string.Empty;
            Port = 0;
            Timeout = TIMEOUT;
            TaskScheduler.UnobservedTaskException += new EventHandler<UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
        }

        public PortScanTest(string host, int port, int timeout = TIMEOUT)
        {
            Host = host;
            Port = port;
            Timeout = timeout;
            TaskScheduler.UnobservedTaskException += new EventHandler<UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
        }

        void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            e.SetObserved();
        }

        public void ScanPort()
        {
            _tokenSource = new CancellationTokenSource();

            Task t = new Task(() =>
            {
                IPAddress address;
                string msg = string.Empty;
                bool success = false;

                try
                {
                    address = Host.GetIPAddress();
                    if (address != null)
                    {
                        if (address.ToString().ScanPort(Port, Timeout))
                        {
                            if (!Host.IsIPAddress())
                            {
                                msg = "Success connected to " + Host + " on port " + Port.ToString() + " (" + address.ToString() + ":" + Port.ToString() + ")";
                            }
                            else
                            {
                                msg = "Success connected to " + Host + " on port " + Port.ToString();
                            }

                            success = true;
                        }
                        else
                        {
                            if (!Host.IsIPAddress())
                            {
                                msg = "Failed to connected to " + Host + " on port " + Port.ToString() + " (" + address.ToString() + ":" + Port.ToString() + ")";
                            }
                            else
                            {
                                msg = "Failed to connected to " + Host + " on port " + Port.ToString();
                            }

                            success = false;
                        }
                    }
                    else
                    {
                        success = false;
                        msg = "Unable to resolve host " + Host;
                    }
                }
                catch (Exception ex)
                {
                    success = false;
                    msg = GetErrorMessage("Port scan failed for " + Host + " on port " + Port.ToString(), ex);
                }

                OnScanCompete(success, msg);
            }, _tokenSource.Token);

            t.Start();
        }

        private string GetErrorMessage(string baseMsg, Exception innerEx)
        {
            string err = baseMsg;
            Exception ex = innerEx;

            while (ex != null)
            {
                err += "\r\n" + ex.Message;
                ex = ex.InnerException;
            }

            return err;
        }

        public void Cancel()
        {
            if (_tokenSource.Token.CanBeCanceled) _tokenSource.Cancel();
        }

        private void OnScanCompete(bool success, string results)
        {
            var handler = ScanComplete;
            if (handler != null) ScanComplete(this, new PortScanResults(success, results));
        }
    }

    class PortScanResults : EventArgs
    {
        public bool Success { get; private set; }
        public string Results { get; private set; }

        public PortScanResults(bool success, string results)
        {
            Success = success;
            Results = results;
        }
    }
}
